wildpocketsfandomcom-20200213-history
Programming Manual Chap. 19
The Central Database Wild Pockets games can store data in a central database on the Wild Pockets servers. The database is a key-value storage system that allows games to record data and retrieve it later. The database can be used for numerous purposes: *To store high scores. *To store saved-game data, if the data is simple. *To store achievements and ranks. There are many other potential uses. It is important to remember that quantity must be kept reasonably limited - it is OK to store a few kilobytes per developer per user, but not more than that. The system may in time use a quota system to enforce this limitation. Per-Player Storage Each player has his own space in the database. Whenever a player stores a record, it goes into that player's space. Players cannot overwrite or alter each other's spaces. Because of this, a player must be logged in to store anything in the database. In effect, my password protects access to my own private space in the database. Most queries only touch my own personal space. However, a few queries may span player boundaries. For example, the queries to fetch a high-score table may scan all the players. Because the data is segregated by player ID, the Lua class for accessing the database is called class 'Player'. Storing Data Records Data records always contain these fields: Key Fields: Player - name of the player who was playing. Developer - name of the developer who wrote the game. Game - name of the game that stored the data. Key - a string of the game's choosing. Value Fields: Date - date that this record was stored. Private - a flag, determines whether other players can see this record. Value - either a number or a string, of the game's choosing. The Lua functions that create a data record are as follows: Player.putPlayerStringData(game,private,key,value,callback) Player.putPlayerNumberData(game,private,key,value,callback) The Key and Value parameters are what this is all about. The Key parameter is a string that lets you classify what kind of data it is. Key might be "score", or "lives remaining", or the like. You can choose any string you desire. The Value parameter is the data that is being stored - again, you can store anything you desire. The Value parameter must be a Lua string if you are using putPlayerStringData, or a Lua number if you are using putPlayerNumberData. The Game parameter allows two pockets to share data. For example, you might have two pockets: "HackDungeon" and "HackDungeon Character Editor." They might both store data records in the central database under the game-name "HackDungeon" so that they can share data. Only pockets created by the same developer can share data in this way. You can pass in nil for game, in which case it will use the pocket's file name as the game name. The Private parameter, if true, prevents other players from reading the current player's data. Use this most of the time: players expect privacy most of the time. Remember, they may be more sensitive than you are. For example, some players won't want anyone to know that they're playing a female character in HackDungeon. The exception is if you deliberately want to broadcast data to the player base as a whole. For example, if you want to implement a high-score table, the score-record would need to be marked public. Other than that, you should mark everything private. The callback parameter is a function that takes a single parameter 'errormessage'. If the storage is successful, the callback will be invoked with a nil error message. If the storage fails (which it might, if the internet is temporarily down), then the callback will be passed an error message string. Remember, these functions require communication over the internet, which can be slow, so it may take some time before the callback gets invoked. As you can see, the player name is not specified in the call. It implicitly records the userID of the currently logged-in player. The functions won't work at all if the player is not logged in. See the documentation of the Payment system for information about how to get the player logged in. The developer name and date are not specified in the Lua methods - these are stored automatically. When a record is stored, the system first checks to see if a previous record exists with the same Player, Developer, Game, and Key. If so, the old record is replaced. Otherwise, a new record is created. Valid Characters The game name and key must be strings of printable ascii characters. That includes letters, numbers, punctuation marks, and space. It does not include tabs, newlines, carriage returns, or any other control character. It also does not include unicode characters. The value field is 8-bit clean. Any Lua string can be stored in the value field, it will be retrieved uncorrupted. Simple Retrieval After storing a data record, you may retrieve it again using these functions: Player.getPlayerStringData(game,user,key,callback) Player.getPlayerNumberData(game,user,key,callback) As you can see, this differs from storage in that you can specify the name of another user to retrieve data from. If you do not specify a user (ie, pass in nil), it will use the currently logged-in user. It is possible to read another user's data - it's just not possible to write to another user's data. You can only retrieve data from another user if it was stored without the private flag. Most data should be stored private, only data which is intended to be displayed on web-pages should be marked public. These functions don't return the data. This is important: it takes time to retrieve data over the internet. These functions cannot return the data, they don't have it yet. All they can do is send off the query. When the reply finally comes back, the callback gets invoked. The callback for these functions takes two parameters: the retrieved data, and an errormessage. If the errormessage is nil, it means the retrieval was successful. High Score Tables The following function is intended to make it feasible to display high score tables: Player.getTopPlayerNumberData(game,key,number,callback) For example, if you were to call getTopPlayerNumberData(nil,"score",10,callback), it would scan all players to find all record marked "score" and marked with the current game. It would return the 10 largest, in the form of a table. See the API reference for more information. Data which is marked private will be ignored by this function. It will return the top N public records. Coming Soon Additional queries are likely to be coming soon - including ones that allow you to retrieve the most recent piece of information stored with a given key. We are implementing an achievement system that will allow games to more easily associate achievements with in-game events, and post those achievements for public viewing. Dealing with Hacking Because Wild Pockets games run on the customer's computer, they can be hacked. It is possible for a player with sufficient hacking tools to store anything he desires into the database. Remember, though, that data is stored on a per-player basis. A player has to log in to alter his space in the database. Therefore, a hacker can only corrupt his own portion of the database. He cannot corrupt the database as a whole. Still, having a user with fake data could cause problems. For example, a high-score list can be generated by scanning the database for the 10 highest scores. That might include the hacker's score, which of course, might have been faked. To solve this problem, it may at times be necessary to detect that a player has hacked his own portion of the database, and ban that player. Once his portion of the database has been stripped out, the system should return to normal. We are planning a 'ban' mechanism that will make it possible to strip hackers out of the database. However, banning is not implemented yet. Atomicity and Ordering When you perform multiple put-operations at the same time, they could get performed in any order. The system makes no guarantee that they will be performed in the order that you "expect" them to. For example, let's say you execute this code: Player.putPlayerStringData(nil, true, "weapon", "sword", callback) Player.putPlayerStringData(nil, true, "armor", "chainmail", callback) In this example, the armor might get recorded to the database before the weapon. If this is a problem for your game, you can use the callbacks to guarantee that one write is finished before starting the next. Things get even more complicated when you remember that it is possible for the player to run two copies of your game at the same time (usually, by opening two browser windows). This can make ordering issues even more complicated. For example, suppose the second copy of the game tries to execute this code: Player.putPlayerStringData(nil, true, "weapon", "arrow", callback) Player.putPlayerStringData(nil, true, "armor", "leather", callback) The four puts could occur in any order, you could even end up with a mix in the end (ie, database says weapon:arrow and armor:chainmail). The easiest way to avoid ordering issues is that in cases where you need atomicity, put everything into a single key, like this: Player.putPlayerStringData(nil, true, "equipment", "weapon:sword armor:chainmail", callback)